package br.ufpe.abaco.ParserGen;

import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Vector;

import br.ufpe.abaco.ParserGen.lexer.LexerFacade;
import br.ufpe.abaco.ParserGen.lexer.LexicalErrorException;
import br.ufpe.abaco.ParserGen.lexer.RegExp;
import br.ufpe.abaco.ParserGen.lexer.Token;
import br.ufpe.abaco.ParserGen.parser.BNFGrammar;
import br.ufpe.abaco.ParserGen.parser.GrammarSymbol;
import br.ufpe.abaco.ParserGen.parser.NonTerminalSymbol;
import br.ufpe.abaco.ParserGen.parser.Parser;
import br.ufpe.abaco.ParserGen.parser.Production;
import br.ufpe.abaco.ParserGen.parser.SemanticAction;
import br.ufpe.abaco.ParserGen.parser.TerminalSymbol;
import br.ufpe.abaco.ParserGen.parser.TokenAction;

public class ParserGen implements ParserGenFacade {

	static RegExpCompiler regExpCompiler = new RegExpCompiler();

	HashMap definedSymbols = new HashMap();

	LexerFacade aLexico = new LexerFacade();
	BNFGrammar aSintatico = new BNFGrammar();
	Parser parser;

	void invalidateParser() {
		parser = null;
	}

	boolean generateTree = false;
	public void generateTree(boolean flag) {
		generateTree = flag;
	}

	HashMap definedConstants = new HashMap();
	HashMap definedConstantsInverse = new HashMap();
	int lastDefinedIndex = 0;

	public String anonymousSymbol() {
		return "__Unamed" + (lastDefinedIndex++);
	}
	public String anonymousTerminal(String regExp, boolean returnToken)
		throws CompilerException {
		String name = anonymousSymbol();
		terminal(name, regExp, returnToken);
		return name;
	}
	public String failSafeAnonymousTerminal(
		String regExp,
		boolean returnToken) {
		try {
			String name = anonymousSymbol();
			terminal(name, regExp, returnToken);
			return name;
		} catch (CompilerException ex) {
			System.err.println(
				"System Error: Automatic Generated Code contains an error");
			System.err.println("that was not detected by the code generator:");
			System.err.println("INVALID REGEXP: " + regExp);
			System.err.println("Please contact support people ");
			return anonymousSymbol();
		}
	}

	
	TokenAction actionReturn = new TokenAction() {
		public Object act(Token tk) throws LexicalErrorException {
			return tk.getValue();
		}
	};
	public void setDefaultActionReturn(TokenAction ac) {
		actionReturn = ac;
	}
	TokenAction actionNoReturn = new TokenAction() {
		public Object act(Token tk) throws LexicalErrorException {
			return null;
		}
	};
	public void setDefaulActionNoReturn(TokenAction ac) {
		actionNoReturn = ac;
	}
	
	
	public String constantString(String value) {
		return constantString(value,false);
	}
	public String constantString(String value,final boolean returnToken) {
		if (definedConstants.containsKey(value))
			return (String) definedConstants.get(value);
		String novo = "__Unamed" + (lastDefinedIndex++);
		definedConstants.put(value, novo);

		TerminalSymbol term = aSintatico.getTerminal(novo);
		definedSymbols.put(novo, term);
		if (returnToken)
  		   term.setAction(actionReturn);
		else
		   term.setAction(actionNoReturn);
		aLexico.insertRule(RegExp.string(value), term);
		//term.priority(1);
		return novo;
	}

	public void terminal(String t, RegExp regExp, TokenAction ac) {
		TerminalSymbol term = aSintatico.getTerminal(t);
		term.setAction(ac);
		definedSymbols.put(t, term);
		aLexico.insertRule(regExp, term);
	}
	public void terminal(String t, RegExp regExp, boolean returnToken) {
		TerminalSymbol term = aSintatico.getTerminal(t);
		if (returnToken)
	  		   term.setAction(actionReturn);
			else
			   term.setAction(actionNoReturn);
		definedSymbols.put(t, term);
		//term.priority(2);

		aLexico.insertRule(regExp, term);
	}

	public void terminal(String t, String regExp, TokenAction returnToken) 	throws CompilerException {
		//System.out.println("Terminal " + t + " ==> " + regExp);
		RegExp r = regExpCompiler.compile(regExp);
		terminal(t, r, returnToken);
    }
	public void terminal(String t, String regExp, boolean returnToken)
		throws CompilerException {

		//System.out.println("Terminal " + t + " ==> " + regExp);
		RegExp r = regExpCompiler.compile(regExp);
		terminal(t, r, returnToken);
	}

	public void failsafeTerminal(
		String t,
		String regExp,
		boolean returnToken) {
		try {
			terminal(t, regExp, returnToken);
		} catch (CompilerException ex) {
			System.out.println("System fatal error on : ");
			ex.printStackTrace();
			System.exit(-1);
		}
	}

	public void setStartSymbol(String n) {
		NonTerminalSymbol nS = aSintatico.getNonTerminal(n);
		aSintatico.startSymbol(nS);
		invalidateParser();
	}

	public void nonTerminal(String n) {
		NonTerminalSymbol nS = aSintatico.getNonTerminal(n);
		definedSymbols.put(n, nS);
		invalidateParser();
	}

	public void comment(String regExp) throws CompilerException {
		RegExp r = regExpCompiler.compile(regExp);
		aLexico.insertBlank(r);
	}
	public void failsafeComment(String regExp) {
		try {
			comment(regExp);
		} catch (CompilerException ex) {
			System.out.println("System fatal error on : ");
			ex.printStackTrace();
			System.exit(-1);
		}

	}

	public SemanticAction actionGenerateList(final Object op) {
		return new SemanticAction() {
			public Object act(Object args[]) {
				List result;
				if (op != null)
					result = new List(op, null);
				else
					result = null;

				for (int c = args.length - 1; c >= 0; c--)
					if (args[c] != null)
						if (args[c] instanceof List)
							result = List.concat((List) args[c], result);
						else
							result = new List(args[c], result);
				return result;
			}
		};
	}
	public SemanticAction actionGenerateTree(final Object op) {
		return new SemanticAction() {
			public Object act(Object args[]) {
				List result = null;

				for (int c = args.length - 1; c >= 0; c--)
					if (args[c] != null)
						if (args[c] instanceof List)
							result = List.concat((List) args[c], result);
						else
							result = new List(args[c], result);
				return new List(op, null, result);
			}
		};
	}

	public String[] breakString(String s) {
		Vector result = new Vector();
		StringTokenizer tk = new StringTokenizer(s);
		while (tk.hasMoreTokens()) {

			String t = tk.nextToken();
			result.addElement(t);
		}
		return (String[]) result.toArray(new String[result.size()]);
	}

	public void addProduction(String left, String right, final Object op) {
		addProduction(left, breakString(right), op);
	}
	
	public void addProd(String left, String r1,final Object op) {
		addProduction(left,new String[] {r1}, op);
	}
	public void addProd(String left, String r1, String r2, final Object op) {
		addProduction(left,new String[] {r1,r2}, op);
	}
	public void addProd(String left, String r1, String r2,String r3, final Object op) {
		addProduction(left,new String[] {r1,r2,r3}, op);
	}
	public void addProd(String left, String r1, String r2,String r3,String r4, final Object op) {
		addProduction(left,new String[] {r1,r2,r3,r4}, op);
	}
	public void addProd(String left, String r1, String r2,String r3,String r4,String r5, final Object op) {
		addProduction(left,new String[] {r1,r2,r3,r4,r5}, op);
	}
	
	public void addProduction(String left, String right[], final Object op) {
		int c;
		NonTerminalSymbol nLeft = aSintatico.getNonTerminal(left);

		GrammarSymbol gRight[] = new GrammarSymbol[right.length];

		for (c = 0; c < right.length; c++) {
			String t = right[c];
			if (t.length() > 2 && t.startsWith("'") && t.endsWith("'"))
				t = constantString(t.substring(1, t.length() - 1));
			Object o = definedSymbols.get(t);
			if (o == null) {
				System.out.print(
					"Erro no simbolo " + right[c] + " na producao : " + left + " -> ");
				for (c=0;c<right.length;c++)
					System.out.print(right[c]+" ");
				System.out.println();
				return;
				
			}
			gRight[c] = (GrammarSymbol) o;
		}
		Production pr = aSintatico.addProduction(nLeft, gRight);
		if (op instanceof SemanticAction)
			pr.setSemanticAction((SemanticAction)op);
		else if (generateTree)
			pr.setSemanticAction(actionGenerateTree(op));
		else
			pr.setSemanticAction(actionGenerateList(op));
		invalidateParser();

	}

	public void addAssociativeProduction(String left,String right,final Object op) {
		addAssociativeProduction(left, breakString(right), op);

	}

	public void addAssociativeProduction(String left,String right[],final Object op) {
		String nonTerminal = anonymousSymbol();
		nonTerminal(nonTerminal);
		int start = -1;
		int end = -1;
		int size;
		int c;
		for (c = 0; c < right.length && start == -1; c++)
			if (right[c].equals(left))
				start = c + 1;
		for (; c < right.length && end == -1; c++)
			if (right[c].equals(left))
				end = c;
		size = end-start+1;
		if (size<0 || end <= start) {
			addProduction(left,right,op);
			return;
		}
		String nRight[] = new String[size];
		for (c=0;c<size;c++)
			nRight[c] = right[c+start];
		addProduction(nonTerminal,nRight,op);
		
		nRight = new String[size+1];
		for (c=0;c<size;c++)
			nRight[c] = right[c+start];
		nRight[c] = nonTerminal;
		addProduction(nonTerminal,nRight,op);

		nRight = new String[right.length-size+1];
		for (c=0;c<start;c++)
			nRight[c]=right[c];
		nRight[c++] = nonTerminal;
		for (int c2=end+1;c2<right.length;c2++)
			nRight[c++] = right[c2];
		addProduction(left,nRight,null);
	}

	public void processCompiler() {
		//System.out.println("Processando Gramatica : " + aSintatico.getStartSymbol());
		parser = new Parser(aSintatico);
	}

	Token tokens[];
	public Token[] getTokens() { return tokens;} 
	public Object compile(String source,String symbol) throws CompilerException {
		tokens = aLexico.analyse(source);
		//System.out.println("Tokens = " + tokens[0]);
		//System.out.println("Parser = " + parser);
		return parser.parse(tokens, symbol, Parser.BACKTRACKING_PARSING, false);
	}
	public Object compile(String source) throws CompilerException {
		tokens = aLexico.analyse(source);
		//System.out.println("Tokens = " + tokens[0]);
		//System.out.println("Parser = " + parser);
		return parser.parse(tokens, Parser.BACKTRACKING_PARSING, false);
	}

	public String toString() {
		return aSintatico.toString();
	}

	public static void main(String args[]) throws CompilerException {
		ParserGen c = new ParserGen();

		c.generateTree(true);

		c.comment(" ");
		c.terminal("n2", "10", true);
		c.terminal("n", "[0-9]+", true);
		c.terminal("id", "[a-z]+", true);

		c.nonTerminal("Exp");
		c.nonTerminal("Com");

		c.addProduction("Exp", "n", "num");
		c.addProduction("Exp", "id", "id");
		c.addProduction("Exp", "Exp '+' Exp", "sum");
		c.addProduction("Exp", "'(' Exp ')'", "par");
		c.addProduction("Exp", "Exp '*' Exp", "prod");
		c.addProduction("Exp", "Exp '-' Exp", "diff");
		
		c.addProduction("Com", "id '=' Exp","assign");
		c.addProduction("Com", "Com ';' Com","seq");
		c.setStartSymbol("Exp");
		c.setStartSymbol("Com");

		c.processCompiler();

		List l = (List)c.compile("10 + 5 + 4","Exp");
		System.out.println("Resultado = " + l);
		
		l = (List)c.compile("a = 10 + x ; b = b + 1","Com");
		System.out.println("Resultado = " + l);

	}

	static void teste() {
		Object definedOperators[] = new Object[100];

	}

}
